import json
import re


class HttpItemTransform:
    ItemHTTPStream_STATES_start = 1
    ItemHTTPStream_STATES_PASS = 8
    ItemHTTPStream_STATES_req_body = 3
    ItemHTTPStream_STATES_res_body = 6
    ItemHTTPStream_STATES_req = 2
    ItemHTTPStream_STATES_res = 5
    ItemHTTPStream_STATES_res_body_chunk = 7
    ItemHTTPStream_STATES_pass = 8
    ItemHTTPStream_STATES_req_body_chunk = 4
    contentLength = {}
    transferEncoding = {}
    contentType = {}
    buffer_stream = b''
    runningStreams = 0
    startPos = [0, 0]
    states = [1, 1]
    bodyNum = 0
    itemPos = 0
    method = ''
    url = ''
    code: int = 0
    res = []

    def __init__(self):
        f = open("incoming.json", "r")
        content = json.loads(f.read())
        f.close()

        for i, item in enumerate(content):
            item['data'] = bytes(item['data'])
            item['client'] = (i % 2)
            self.startPos[item['client']] = 0
            pos = 0
            while pos < len(item['data']):
                state = self.states[item['client']]
                if state == self.ItemHTTPStream_STATES_PASS:
                    self.res.append(
                        {'client': item['client'],
                         'ts': item['ts'],
                         'data': item['data'],
                         'itemPos': self.itemPos})
                    self.itemPos = self.itemPos + 1
                    break
                if state == self.ItemHTTPStream_STATES_req_body or state == self.ItemHTTPStream_STATES_res_body:
                    pos = self.processBody(item, pos)
                else:
                    pos = self.processText(item, pos)
        print(self.res)

    def processText(self, item, pos):
        end_pos = pos
        client = item['client']
        data = item['data']
        while end_pos < len(data) and data[end_pos] != 0x0a:
            end_pos += 1
        if end_pos > pos and data[end_pos - 1] == 0x0d:
            line = data[pos:end_pos - 1].decode('utf-8', errors='ignore')
        else:
            line = data[pos:end_pos].decode('utf-8', errors='ignore')
        if self.states[client] == self.ItemHTTPStream_STATES_start:
            upper = line
            self.contentLength[str(client)] = 0
            self.transferEncoding[client] = ''
            if upper.startswith('CONNECT'):
                self.states[client] = self.ItemHTTPStream_STATES_req
                self.method = 'CONNECT'
            elif upper.startswith('HTTP'):
                self.states[client] = self.ItemHTTPStream_STATES_res
                parts = upper.split()
                self.code = int(parts[1])
            elif len(upper) > 0:
                self.states[client] = self.ItemHTTPStream_STATES_req
                self.method = upper.split(' ')[0]
                self.url = upper.split(' ')[1]
        elif self.states[client] == self.ItemHTTPStream_STATES_req:
            if len(line) == 0:
                if re.match(r'^(GET|HEAD|DELETE|TRACE)$', self.method):
                    self.states[client] = self.ItemHTTPStream_STATES_start
                elif re.match(r'^CONNECT$', self.method):
                    self.states[client] = self.ItemHTTPStream_STATES_pass
                elif self.transferEncoding[client] == 'CHUNKED':
                    self.states[client] = self.ItemHTTPStream_STATES_req_body_chunk
                else:
                    self.states[client] = self.ItemHTTPStream_STATES_req_body
                self.add(item, end_pos + 1)
            upper = line.upper()
            if upper.startswith('CONTENT-LENGTH'):
                self.contentLength[client] = +upper[15:]
            elif upper.startswith('CONTENT-TYPE'):
                self.contentType[client] = upper[14:]
            elif upper.startswith('TRANSFER-ENCODING'):
                self.transferEncoding[client] = upper[19:]
        elif self.states[client] == self.ItemHTTPStream_STATES_req_body_chunk:
            if len(line) != 0:
                try:
                    self.contentLength[client] = int(line, 16)
                    if self.contentLength[client] == 0:
                        self.msgEnd(item)
                except ValueError:
                    print('Missing Chunk Length')
            else:
                self.states[client] = self.ItemHTTPStream_STATES_res_body
        elif self.states[client] == self.ItemHTTPStream_STATES_res:
            if len(line) == 0:
                if self.code / 100 == 1 or self.code == 204 or self.code == 304:
                    self.states[client] = self.ItemHTTPStream_STATES_start
                elif re.match(r'^CONNECT$', self.method):
                    self.states[client] = self.ItemHTTPStream_STATES_pass
                elif self.transferEncoding[client] == 'CHUNKED':
                    self.states[client] = self.ItemHTTPStream_STATES_res_body_chunk
                else:
                    self.states[client] = self.ItemHTTPStream_STATES_res_body
                self.add(item, end_pos + 1)
            upper = line.upper()
            if upper.startswith("CONTENT-LENGTH"):
                self.contentLength[client] = +int(upper[15:].strip())
            elif upper.startswith("CONTENT-TYPE"):
                self.contentType[client] = upper[14:]
            elif upper.startswith("TRANSFER-ENCODING"):
                self.transferEncoding[client] = upper[19:]
        elif self.states[client] == self.ItemHTTPStream_STATES_res_body_chunk:
            if len(line) != 0:
                try:
                    self.contentLength[client] = int(line, 16)
                    if self.contentLength[client] == 0:
                        self.msgEnd(item)
                    else:
                        self.states[client] = self.ItemHTTPStream_STATES_res_body
                except ValueError:
                    print('Missing Chunk Length')
        return end_pos + 1

    def add(self, item, end_pos):
        client = item['client']
        self.itemPos = self.itemPos + 1
        self.startPos[client] = end_pos
        print({
            'client': client,
            'ts': item['ts'],
            'data': item['data'][self.startPos[client]: end_pos],
            'itemPos': self.itemPos
        })

    def msgEnd(self, item):
        self.states[item['client']] = self.ItemHTTPStream_STATES_start
        self.buffer_stream = b''

    def processBody(self, item, pos):
        client = item['client']
        data = item['data']
        if self.contentLength[client]:
            avail = len(data) - pos
            used = min(avail, self.contentLength[client])
            if self.buffer_stream == b'':
                self.runningStreams = self.runningStreams + 1
                self.bodyNum = self.bodyNum + 1
                self.itemPos = self.itemPos + 1
                info = {
                    'bodyNum': self.bodyNum,
                    'bodyName': self.url.split('/')[-1].split('=')[-1] if self.url else None,
                    'bodyType': 'file',
                    'itemPos': self.itemPos
                }
                if info['bodyName'] == "" or len(info['bodyName']) == 0:
                    info['bodyName'] = f"{info['bodyType']}{info['bodyNum']}"
                if re.match(r'^image', self.contentType[client], re.IGNORECASE):
                    info['bodyType'] = 'image'
                elif re.match(r'^text', self.contentType[client], re.IGNORECASE):
                    info['bodyType'] = 'text'
                body = data[pos:pos + used]
                # gzip.decompress(body)
                print(body)
                pos += used
                self.contentLength[client] -= used
            if self.contentLength[client] == 0:
                if self.transferEncoding[client] == 'CHUNKED':
                    self.states[client] = self.ItemHTTPStream_STATES_res_body_chunk
                else:
                    self.msgEnd(item)
        return pos


if __name__ == '__main__':
    HttpItemTransform()
